#include "StdAfx.h"
#include "utils.h"
#include "primitives.h"
#include "overlapchecks.h"
#include "intersectionchecks.h"
#include "unprojectionchecks.h"
#include "bvtree.h"
#include "geometry.h"
#include "singleboxtree.h"
#include "aabbtree.h"
#include "obbtree.h"
#include "trimesh.h"
#include "heightfieldbv.h"
#include "heightfieldgeom.h"
#include "voxelbv.h"
#include "voxelgeom.h"
#include "physicalplaceholder.h"
#include "rigidbody.h"
#include "physicalentity.h"
#include "softentity.h"
#include "ropeentity.h"
#include "rigidentity.h"
#include "articulatedentity.h"
#include "geoman.h"
#include "physicalworld.h"
//#define USE_JOB_DUMPS
#ifdef USE_JOB_DUMPS
#include "boxgeom.h"
#include "cylindergeom.h"
#include "spheregeom.h"
#include "raybv.h"
#include "raygeom.h"
#endif

#include <IJobManager.h>
#include <IJobManager_JobDelegator.h>

SPU_LOCAL SPU_DOMAIN_LOCAL CHeightfield *g_pHFbuf[MAX_PHYS_THREADS+1];
#define g_pHF g_pHFbuf[iCaller]

#ifndef __SPU__
extern float getHFPatchHeight(int ix,int iy);
extern unsigned char getHFPatchSurfType(int ix,int iy);
#endif 






#define JOB_COUNT 6
#define JOB_SLOT_COUNT 512
#define MAX_JOB_SIZE_INTERSECT (48*1024)
#define MAX_JOB_SIZE_INTERSECT_AUX (8*1024)
#define MAX_JOB_SIZE_SOLVER (200*1024) // note: reduce the size of unused ones in PHYS_JOB_SIMULATION mode on xbox
#define MAX_JOB_SIZE_CLOTH (80*1024)
#define MAX_JOB_SIZE_ROPE (70*1024)
#define MAX_JOB_SIZE_FS (60*1024)
#define MAX_SUBMISSION_RETRIES 1

#ifdef USE_JOB_DUMPS
template<class F> F *patch_vmt(F* pObj)
{
	F tpl;
	*(int*)pObj = *(int*)&tpl;
	return pObj;
}
#else
#define patch_vmt(a)
#endif

//need a cache bypassing version of writing to a volatile int ptr
ILINE void SignalOutput(volatile int* pWritten, const int val)
{























	*pWritten = val;

}









char *g_jobName[JOB_COUNT] = { "RB_GeomIntersect", "RB_Solver", "Cloth",
															 "Rope", "Featherstone", "RB_PrimIntersect" };













































































	#define sys_timer_subusleep_inldb16cyc(ms)


#ifdef USE_PHYS_JOBS

struct used_geom {
	used_geom() {}
	used_geom(CGeometry *_pgeom,int _ioffs) { pgeom=_pgeom; ioffs=_ioffs; }
	CGeometry *pgeom;
	int ioffs;
};
















# define CryEventSimple CryEvent


static SPU_LOCAL CryEventSimple g_someJobDone;
CGeometry *LoadGeomFromMem(CMemStreamA &stm);

#if defined(PHYS_JOB_PROFILER_ENABLED)
# define SPU_NO_SW_CACHE JobManager::SPUBackend::eCM_4
#else
# define SPU_NO_SW_CACHE JobManager::SPUBackend::eCM_None
#endif 
struct SJobData;
void RB_KernelJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void PrimIntersectJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void GeomIntersectJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void SolverJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void ClothJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void RopeJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void FeatherstoneJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);


























struct SJobData {
	volatile int written;
	int bWorking;
	int read;
	int szin,szin0;
	char *bufin;
	int szout;
	char *bufout;
	void *pent0last;
	CMemStream *stmAux;
	used_geom *pUsedGeoms;
	int nUsedGeoms,nUsedGeomsAlloc;
	int ipass, ientries;
	SAuxillaryJobInfo* auxInfo; 
	volatile int64 ticksStart, ticks, yieldStart;
	SJobData() : stmAux(), ticksStart(), ticks(), yieldStart(), auxInfo() {}
	int has_geom(CGeometry *pGeom) { 
		for(int i=nUsedGeoms-1;i>=0;i--) if (pUsedGeoms[i].pgeom==pGeom)
			return pUsedGeoms[i].ioffs;
		return 0;
	}
#if defined(JOBMANAGER_SUPPORT_PROFILING)
	JobManager::SJobState jobState; // only used for profiling
#endif
} _ALIGN(128);

#ifndef __SPU__
struct SJob {
	SJobData **pActiveJobs; 
	SJobData **pPendingJobs; 
	size_t nActiveJobs,nPendingJobs,nJobSlots;
	int szinMax;
	int ipass;
	int ijob; 
};

#if defined(PHYS_JOB_SIMULATION)
void PrimIntersectJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void GeomIntersectJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void SolverJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void ClothJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void RopeJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
void FeatherstoneJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData);
struct SFakeSPUTask : public IThreadTask {
	SFakeSPUTask() {bStop=0;m_job=0;}
	virtual void OnUpdate() {
		MarkAsPhysWorkerThread(&m_idx);
		while(bStop==0)	{
			m_start.Wait();
			if (m_job) {
				switch (m_job->ipass) { 
					case 0: GeomIntersectJobProc(m_job->bufin,m_job->szin0+4, m_job->bufout,m_job->szout, &m_job->written, NULL); break; 
#ifdef USE_SOLVER_JOB
					case 1: SolverJobProc(m_job->bufin,m_job->szin0, m_job->bufout,m_job->szout, &m_job->written, NULL); break; 
#endif
#ifdef USE_CLOTH_JOB
					case 2: ClothJobProc(m_job->bufin,m_job->szin0, m_job->bufout,m_job->szout, &m_job->written, NULL); break; 
#endif
#ifdef USE_ROPE_JOB
					case 3: RopeJobProc(m_job->bufin,m_job->szin0, m_job->bufout,m_job->szout, &m_job->written, NULL); break; 
#endif
#ifdef USE_FEATHERSTONE_JOB
					case 4: FeatherstoneJobProc(m_job->bufin,m_job->szin0, m_job->bufout,m_job->szout, &m_job->written, NULL); break; 
#endif
					case 5: PrimIntersectJobProc(m_job->bufin,m_job->szin0+4, m_job->bufout,m_job->szout, &m_job->written, NULL); break; 
				}
				m_job=NULL; 
			}
		}
		GetISystem()->GetIThreadTaskManager()->UnregisterTask(this);
		g_someJobDone.Set();
	}
	virtual void Stop() {}
	virtual SThreadTaskInfo* GetTaskInfo() { return &m_TaskInfo; }
	CryEvent m_start; 
	volatile SJobData *m_job;
	int bStop, m_idx;
	SThreadTaskInfo m_TaskInfo;
};
#endif

struct SJobDataManager { 
  struct pool_entry { 
    pool_entry *next, *prev;
		int size; 
  }; 
	pool_entry *freelist; 	
	char *pPool; 
	int nPoolSize,nJobs, nSubmissionRetries; 
	int maxJobSize[JOB_COUNT];
	SJob jobs[JOB_COUNT];




#if defined(PHYS_JOB_SIMULATION)
	SFakeSPUTask *fakeSpus; 
#endif 

	void* allocate(uint32 sz, size_t align) {
		int nsize, oldsize; char* ptr; 
 		for(register pool_entry *entry=freelist; entry; entry=entry->next) {
			if (entry->size >= sz+sizeof(pool_entry)) { 
				ptr=(char*)entry+sizeof(pool_entry)+(entry->size-sz); 
				ptr=(char*)((size_t)ptr & ~(align-1)) - sizeof(pool_entry); 
				nsize = ptr-((char*)entry+sizeof(pool_entry));
				IF (nsize<=0,0) continue; 
				oldsize=entry->size, entry->size=nsize; 
		
				pool_entry *split = new (ptr) pool_entry;
				split->prev=NULL; split->next=NULL;
				split->size=((char*)entry+sizeof(pool_entry)+oldsize)-(ptr+sizeof(pool_entry)); 

				return (char*)split + sizeof(pool_entry); 
			}
		} 
		return NULL; 
	}

	void deallocate(void* ptr) { 
		pool_entry *alloc=(pool_entry*)ptr-1, *iter=freelist, *last=freelist;
		for(; iter && iter<alloc; last=iter,iter=iter->next); 
		alloc->next=iter; IF(iter,1) iter->prev=alloc;
		alloc->prev=last; IF(last,1) last->next=alloc;
		merge_freelist(alloc);  
	}

	void split(void* ptr, size_t sz, size_t align) { 
		sz = (sz+(align-1))&~(align-1); 
		pool_entry *main=(pool_entry*)ptr-1, *iter=freelist, *last=freelist; 
		IF (main->size>sz && main->size-sz>sizeof(pool_entry)+4096, 1) { // + 4096 == doesn't make sense to have too small freelist entries
			char *offset = (char*)main+sizeof(pool_entry)+(sz); 
			pool_entry *split = new (offset) pool_entry; 
			split->prev=split->next=NULL;
			split->size=((char*)main+sizeof(pool_entry)+main->size)-((char*)split+sizeof(pool_entry));
			main->size=sz;

			for(; iter && iter<split; last=iter,iter=iter->next); 
			split->next=iter; IF(iter,1) iter->prev=split;
			split->prev=last; IF(last,1) last->next=split;
			merge_freelist(split);
		} 
	}

	void* reallocate(void* ptr, size_t align, size_t nsz) { 
		int WaitForJobFinish(int&,int);
    void* nptr=NULL; 
		while(!(nptr=allocate(nsz, align))) WaitForJobFinish(*(int*)0,1);
		IF (ptr, 1) { 
			memcpy(nptr, ptr, ((pool_entry*)ptr-1)->size); 
			deallocate(ptr); 
		}
		return nptr; 
	}

	void merge_freelist(pool_entry* alloc) { 
		bool changed;
		do { 
			changed=false; 
			if (alloc->prev && ((char*)alloc->prev+alloc->prev->size+sizeof(pool_entry))==(char*)alloc) { 
				alloc->prev->next = alloc->next; 
				alloc->prev->size = (size_t)((char*)alloc+alloc->size+sizeof(pool_entry)-((char*)alloc->prev+sizeof(pool_entry))); 
				alloc = alloc->prev; 
				changed=true; 
			} 
			if (((char*)alloc+alloc->size+sizeof(pool_entry))==(char*)alloc->next) { 
				alloc->size = (size_t)((char*)alloc->next+alloc->next->size+sizeof(pool_entry)-((char*)alloc+sizeof(pool_entry))); 
				alloc->next = alloc->next->next; 
				changed=true; 
			} 
		} while(changed); 
	} 

	void Initialize(int numJobs, int size) { 
		if (pPool) {
			for (int i=0; i<JOB_COUNT; maxJobSize[i++] = -1);
			if (freelist->next) std::abort(); // pool must have no job data entries left  
			CryModuleMemalignFree(pPool), pPool = NULL; 
			freelist=NULL; 
			for (size_t i=0; i<JOB_COUNT; ++i) { 
				SJob &job = jobs[i];
				job.ijob=job.nActiveJobs=job.nPendingJobs=0; 
				delete[] job.pActiveJobs; 
				delete[] job.pPendingJobs; 
			} 



		}
#if defined(PHYS_JOB_SIMULATION) 
		for(int i=0; i<nJobs; i++) {
			fakeSpus[i].bStop=1; fakeSpus[i].m_start.Set();
			g_someJobDone.Wait();
		}
		delete[] fakeSpus; 
#endif
		if (size) { 
			maxJobSize[0] = MAX_JOB_SIZE_INTERSECT-4;
			maxJobSize[1] = MAX_JOB_SIZE_SOLVER-4;
			maxJobSize[2] = MAX_JOB_SIZE_CLOTH-4;
			maxJobSize[3] = MAX_JOB_SIZE_ROPE-4;
			maxJobSize[4] = MAX_JOB_SIZE_FS-4;
			maxJobSize[5] = MAX_JOB_SIZE_INTERSECT-4;

			pPool=(char*)CryModuleMemalign(nPoolSize=size, 128); 
			freelist = new (pPool) pool_entry; 
			freelist->prev=freelist->next=NULL;
			freelist->size=size-sizeof(pool_entry);

      for (size_t i=0; i<JOB_COUNT; ++i) { 
				memset(jobs[i].pActiveJobs=new SJobData*[jobs[i].nJobSlots=JOB_SLOT_COUNT], 0, sizeof(SJobData*)*JOB_SLOT_COUNT);
				memset(jobs[i].pPendingJobs=new SJobData*[jobs[i].nJobSlots], 0, sizeof(SJobData*)*JOB_SLOT_COUNT);
				jobs[i].nActiveJobs=jobs[i].nPendingJobs=0; 
				jobs[i].szinMax=maxJobSize[i]; 
				jobs[i].ijob=jobs[i].ipass=0;
			} 





		}
#if defined(PHYS_JOB_SIMULATION)
		fakeSpus=new SFakeSPUTask[JOB_COUNT];
		SThreadTaskParams ttp;
		ttp.name = "PhysicsSPUThread";
		ttp.nFlags = THREAD_TASK_BLOCKING;
		ttp.nPriorityOff = 1;
		for(int i=0; i<numJobs; i++) { 
			fakeSpus[i].bStop=0;
			fakeSpus[i].m_idx=i+1; 
			fakeSpus[i].m_job=0;
			GetISystem()->GetIThreadTaskManager()->RegisterTask(&fakeSpus[i], ttp);
		}
#endif
		nJobs=numJobs;
    nSubmissionRetries = 0; 
	} 

	SJobData* AcquireJobData(int ipass) { 
		SJobData* pJobData=NULL; 
		SAuxillaryJobInfo* auxInfo=NULL; 
		char *bufin=NULL,*bufout=NULL; 
		int szout=0,nUsedGeomsAlloc=0;
		CMemStream *stmAux=NULL; 
		used_geom *pUsedGeoms=NULL;
		void* alloc=NULL; 

		IF (!(alloc=allocate(sizeof(SJobData),128)),0) goto error; pJobData=new (alloc) SJobData; alloc=NULL;
		IF (!(bufin=(char*)allocate(maxJobSize[ipass], 128)),0) goto error;
		switch(ipass) { 
			if (false) { case 1: IF (!(alloc=allocate(sizeof(CMemStream),8)),0) goto error; stmAux=new (alloc) CMemStream(false); alloc=NULL; break; }
			if (false) { 
				case 0: 
				case 5: 
					IF (!(bufout=(char*)allocate(szout=48<<10, 128)),0) goto error; 
					szout-=10360; /*potential max. output size*/ 
				case 3:
					IF (!(auxInfo=(SAuxillaryJobInfo*)(allocate(sizeof(SAuxillaryJobInfo),128))),0) goto error; 
					auxInfo->rawSpuId=-1;
					auxInfo->lsBufferOffset=auxInfo->lsDoneFlag=auxInfo->lsJobFlag=~0u;
					auxInfo->jobFlag=auxInfo->doneFlag=0;
			}
			IF (!(pUsedGeoms=(used_geom*)allocate(sizeof(used_geom)*(nUsedGeomsAlloc=256),8)),0) goto error;
			break; 
			default: break; 
		} 

		pJobData->bufin=bufin; 
		pJobData->bufout=bufout; 
		pJobData->pUsedGeoms=pUsedGeoms; 
		pJobData->nUsedGeomsAlloc=nUsedGeomsAlloc;
		pJobData->nUsedGeoms=0;
		pJobData->ipass=ipass;
		pJobData->ientries=0;
		pJobData->pent0last=0;
		pJobData->szout=szout;
		pJobData->szin=pJobData->szin0=0;
		pJobData->written=pJobData->read=pJobData->bWorking=0;
		pJobData->stmAux=stmAux;
		pJobData->auxInfo=auxInfo; 
		*(int*)pJobData->bufin = -1;
		return pJobData;

		if (false) { error: 
			if (bufout) deallocate(bufout); 
			if (bufin) deallocate(bufin); 
			if (pJobData) deallocate(pJobData); 	
			if (auxInfo) deallocate(auxInfo); 
			if (stmAux) { stmAux->~CMemStream(); deallocate(pJobData->stmAux);}
			if (pUsedGeoms) deallocate(pUsedGeoms); 
			if (alloc) deallocate(alloc); 
			return NULL; 
		}
	}

	void ReleaseJobData(SJobData *pJobData) { 
		switch(pJobData->ipass) { 
			case 0: case 5: deallocate(pJobData->bufout); case 3: deallocate(pJobData->auxInfo); break;
			case 1: pJobData->stmAux->~CMemStream(); deallocate(pJobData->stmAux); break; 
		} 
		if (pJobData->pUsedGeoms) deallocate(pJobData->pUsedGeoms); 
		deallocate(pJobData->bufin);
		deallocate(pJobData); 
	} 




}; 

static SJobDataManager g_JobMgr; 

int ReportJobContacts(char *bufout);
CBVTree *LoadBVTreeFromMem(CMemStreamA &stm, CTriMesh *pMesh);
void SaveBVTreeToMem(CBVTree *pTree, CMemStreamA &stm);
void SaveGeomToMem(CGeometry *pGeom, CMemStreamA &stm, int bForRays);
int WaitForJobFinish(int&,int);
CMemStream &GetJobAuxStream(int ipass) { return *g_JobMgr.jobs[ipass].pPendingJobs[g_JobMgr.jobs[ipass].ijob]->stmAux; }
int ReadDelayedSolverResults(CMemStream &stm, entity_contact **&pContactsOut,RigidBody **&pBodiesOut);
int GeomOffsInJobBuf(CGeometry *pGeom, int ipass, int ijob) { return g_JobMgr.jobs[ipass].pPendingJobs[ijob]->has_geom(pGeom); }
void SaveGeomToJobBuf(CMemStreamA &stm, CGeometry *pGeom, int ioffs, int ipass, int ijob, int bForRays=0) {
#if defined(MEMSTREAM_DEBUG)
	int tag = MEMSTREAM_DEBUG_TAG; stm.Write(tag); 
#endif 
	SJob& job = g_JobMgr.jobs[ipass]; 
	stm.Write(ioffs);
	if (!ioffs)	{
		if (job.pPendingJobs[ijob]->nUsedGeoms==job.pPendingJobs[ijob]->nUsedGeomsAlloc)
			job.pPendingJobs[ijob]->pUsedGeoms=(used_geom*)g_JobMgr.reallocate(job.pPendingJobs[ijob]->pUsedGeoms, 8, sizeof(used_geom)*(job.pPendingJobs[ijob]->nUsedGeomsAlloc+=64));
		job.pPendingJobs[ijob]->pUsedGeoms[job.pPendingJobs[ijob]->nUsedGeoms++] = used_geom(pGeom,job.pPendingJobs[ijob]->szin+stm.m_iPos);
		SaveGeomToMem(pGeom, stm, bForRays);
	} 
#if defined(MEMSTREAM_DEBUG)
	tag = MEMSTREAM_DEBUG_TAG+1; stm.Write(tag); 
#endif 
}

void CTriMesh::SpuUnfriendly(char* reason, size_t &len) 
{ 
	// keep constants in sync with intersectqueued in jobq.cpp
	int nIdxTri=0,nFeatures=0,nIds=0,nUsedNodes=0,nNodeTris=0,nVtx=0,nTris=0,ijob;
	char token[256]; 
	nNodeTris = GetBVTree()->MaxPrimsInNode();
	nIdxTri = m_nMaxVertexValency+nNodeTris;
	nFeatures = m_nMaxVertexValency;
	nIds = max(m_nMaxVertexValency,nNodeTris);
	nVtx = max(nVtx, m_nVertices);
	nTris = max(nTris, m_nTris);
	ijob = m_pTree->GetType();
	if (iszero(ijob-BVT_OBB)|iszero(ijob-BVT_AABB))
		nUsedNodes += (((CAABBTree*)m_pTree)->m_nNodes-1>>5)+1;
	if (nIdxTri>(128>>1) || nFeatures>(32>>1) || nIds>(128>>1) || nUsedNodes>(240>>1)) { 
		sprintf(token, "geometry too complex|");
		strcat(reason, token); len-=strlen(token);
	}
	if (nVtx>((256*32)>>1)) { 
		sprintf(token, "too many vertices (max:%d)|", (256*32)>>1);
		strcat(reason, token); len-=strlen(token);
	} 
	if (nTris>((400*32)>>1)) { 
		sprintf(token, "too many triangles (max:%d)|", (400*32)>>1);
		strcat(reason, token); len-=strlen(token);
	} 
	
	CMemStreamA stm; 
	SaveGeomToMem(this, stm, 0); 
	if (stm.m_iPos>MAX_JOB_SIZE_INTERSECT>>1) { 
		sprintf(token, "too large (max:%dkb)|", (MAX_JOB_SIZE_INTERSECT>>1)>>10);
		strcat(reason, token); len-=strlen(token);
	} 
}

int CGeometry::IntersectQueued(IGeometry *piCollider, geom_world_data *pdata1,geom_world_data *pdata2, 
															 intersection_params *pip, geom_contact *&pcontacts, void *pent0,void *pent1,int ipart0,int ipart1)
{
	bool bQueue = true;
#ifndef PHYS_JOB_SIMULATION
	JobManager::IJobManager *pJobManager = gEnv->GetJobManager();
	bQueue = pJobManager->InvokeAsSPUJob("RB_PrimIntersect") & pJobManager->InvokeAsSPUJob("RB_GeomIntersect") & pJobManager->InvokeAsSPUJob("RB_Kernel");
#endif
	if (!bQueue || !g_JobMgr.nJobs || pip->bSweepTest) 
		return Intersect(piCollider, pdata1,pdata2, pip, pcontacts);

	CGeometry *pCollider = (CGeometry*)piCollider;
	ReadLockCond lock0(pCollider->m_lockUpdate, isneg(pCollider->m_lockUpdate)^1);
	ReadLockCond lock1(this->m_lockUpdate, isneg(this->m_lockUpdate)^1); 
	int i,ihf,imesh,ijob,ioffs0,ioffs1,size,sizehdr,size0,size1,hfVtx=0,retries=0;
	int itype[2] = { GetType(), pCollider->GetType() };
	bool bKeepPrevContacts = pip->bKeepPrevContacts;
	if (iszero(itype[0]-GEOM_VOXELGRID)|iszero(itype[1]-GEOM_VOXELGRID)) { 
		return Intersect(piCollider, pdata1,pdata2, pip, pcontacts);
	}
	if (ihf = iszero(itype[0]-GEOM_HEIGHTFIELD) | iszero(itype[1]-GEOM_HEIGHTFIELD)*2) {
		geometry_under_test gtest[2];
		geom_world_data *pdata[2] = { pdata1,pdata2 };
		for(i=0;i<2;i++) {
			gtest[i].offset = pdata[i]->offset;
			gtest[i].R = pdata[i]->R;
			gtest[i].scale = pdata[i]->scale;
			gtest[i].rscale = 1.0f/pdata[i]->scale;
			gtest[i].v = pdata[i]->v;
		}
		for(i=0;i<2;i++) {
			gtest[i].offset_rel = ((gtest[i].offset-gtest[i^1].offset)*gtest[i^1].R)*gtest[i^1].rscale;
			(gtest[i].R_rel = gtest[i^1].R.T()) *= gtest[i].R;
			gtest[i].scale_rel = gtest[i].scale*gtest[i^1].rscale;
			gtest[i].rscale_rel = gtest[i^1].scale*gtest[i].rscale;
			gtest[i].sweepstep = 0;
			gtest[i].bStopIntersection = 1;
			gtest[i].iCaller = 0;
		}
		if (pip->bSweepTest) {
			gtest[0].sweepstep = pcontacts[0].vel = gtest[0].v.len();
			gtest[0].sweepdir = gtest[0].v/gtest[0].sweepstep;
			gtest[0].sweepstep *= pip->time_interval;
			gtest[0].sweepdir_loc = gtest[0].sweepdir*gtest[0].R;
			gtest[0].sweepstep_loc = gtest[0].sweepstep*gtest[0].rscale;
		}	
		if (ihf & 1) { 
			if (!PrepareForIntersectionTest(gtest+0, pCollider,gtest+1, true))
			return 0;
			hfVtx = ((CTriMesh*)this)->m_nVertices;
		}
		if (ihf & 2) { 
			if (!pCollider->PrepareForIntersectionTest(gtest+1, this,gtest+0, true))
			return 0;
			hfVtx = ((CTriMesh*)(CTriMesh*)pCollider)->m_nVertices;
	}
		if (hfVtx>500) { 
#     if !defined(__SPU__) && defined(PHYS_JOB_PROFILER_ENABLED)
			if (g_pPhysWorlds[0]) ++g_pPhysWorlds[0]->GetJobProfileInst(0).nFallbacks; 
#     endif 
			return Intersect(piCollider, pdata1,pdata2, pip, pcontacts);
		}
	}
	if (ihf | (imesh = iszero(itype[0]-GEOM_TRIMESH) | iszero(itype[1]-GEOM_TRIMESH)*2)) {
		int nIdxTri=0,nFeatures=0,nIds=0,nUsedNodes=0,nNodeTris=0,nVtx=0,nTris=0;
		CTriMesh *pMesh[2] = { (CTriMesh*)this, (CTriMesh*)pCollider };
		for(i=0;i<2;i++) if ((ihf|imesh)&1<<i) {
			nNodeTris = pMesh[i]->GetBVTree()->MaxPrimsInNode();
			nIdxTri += pMesh[i]->m_nMaxVertexValency+nNodeTris;
			nFeatures += pMesh[i]->m_nMaxVertexValency;
			nIds += max(pMesh[i]->m_nMaxVertexValency,nNodeTris);
			nVtx = max(nVtx, pMesh[i]->m_nVertices);
			nTris = max(nTris, pMesh[i]->m_nTris);
			ijob = pMesh[i]->m_pTree->GetType();
			if (iszero(ijob-BVT_OBB)|iszero(ijob-BVT_AABB))
				nUsedNodes += (((CAABBTree*)pMesh[i]->m_pTree)->m_nNodes-1>>5)+1;
		}
		if (max(max(max(max(max(nIdxTri-128,nFeatures-32),nIds-128),nUsedNodes-240),nVtx-(256*32)),nTris-(400*32))>0) { 
#     if !defined(__SPU__) && defined(PHYS_JOB_PROFILER_ENABLED)
			if (g_pPhysWorlds[0]) ++g_pPhysWorlds[0]->GetJobProfileInst(0).nFallbacks; 
#     endif 
			return Intersect(piCollider, pdata1,pdata2, pip, pcontacts);
		}
	}

	int ipass = (pCollider->IsAPrimitive() & IsAPrimitive()) * 5;
	SJob& job = g_JobMgr.jobs[ipass]; 
	CMemStreamA stmSizer;
	
	SaveGeomToMem(this, stmSizer, 0); size0=stmSizer.m_iPos+16; stmSizer.m_iPos=0;
	SaveGeomToMem(pCollider, stmSizer, 0); size1=stmSizer.m_iPos+16; 
	sizehdr = sizeof(int)*2+sizeof(geom_world_data)*2+sizeof(intersection_params)+(sizeof(void*)+sizeof(int))*2;
	if (sizehdr+size0+size1 > job.szinMax) { 
#if !defined(__SPU__) && defined(PHYS_JOB_PROFILER_ENABLED)
		if (g_pPhysWorlds[0]) ++g_pPhysWorlds[0]->GetJobProfileInst(0).nFallbacks; 
#endif 
		return Intersect(piCollider, pdata1,pdata2, pip, pcontacts);
	}

retry: 
	if ((ijob=job.ijob)<job.nPendingJobs && pent0==job.pPendingJobs[ijob]->pent0last) {
		ioffs0 = (ihf & 1) ? 0 : job.pPendingJobs[ijob]->has_geom(this);
		ioffs1 = (ihf & 2) ? 0 : job.pPendingJobs[ijob]->has_geom(pCollider);
		size = sizehdr + (size0 & ioffs0-1>>31) + (size1 & ioffs1-1>>31);
		if (job.pPendingJobs[ijob]->szin+size > job.szinMax) {
			pip->bKeepPrevContacts = false;
			goto findempty;
		}
		pip->bKeepPrevContacts = true;
	}	else {
		findempty:
		for(ijob=i=0;i<job.nPendingJobs;i++) 
			if (!job.pPendingJobs[i]->bWorking && job.szinMax-job.pPendingJobs[i]->szin > job.szinMax-job.pPendingJobs[ijob]->szin)
				ijob = i;
		if (ijob>=job.nPendingJobs || 
			 ((ioffs0 = (ihf & 1) ? 0 : job.pPendingJobs[ijob]->has_geom(this)),
			  (ioffs1 = (ihf & 2) ? 0 : job.pPendingJobs[ijob]->has_geom(pCollider)),
			  (size = sizehdr + (size0 & ioffs0-1>>31) + (size1 & ioffs1-1>>31)),
			  job.pPendingJobs[ijob]->szin+size > job.szinMax)) 
		{
			WaitForJobFinish(*(int*)0,0); 
			if (job.nPendingJobs<job.nJobSlots) { 
				if (!(job.pPendingJobs[ijob=job.nPendingJobs++]=g_JobMgr.AcquireJobData(ipass))) { 
					--job.nPendingJobs; 
					IF (retries++==0 && g_JobMgr.nSubmissionRetries++<MAX_SUBMISSION_RETRIES, 1) { WaitForJobFinish(*(int*)0,1); goto retry; }
					goto fallback; 
				}
			} else { fallback: 
				pip->bKeepPrevContacts = bKeepPrevContacts;
#       if !defined(__SPU__) && defined(PHYS_JOB_PROFILER_ENABLED)
				if (g_pPhysWorlds[0]) ++g_pPhysWorlds[0]->GetJobProfileInst(ipass).nFallbacks; 
#       endif 
				return Intersect(piCollider, pdata1,pdata2, pip, pcontacts);
      }
			ioffs0=ioffs1 = 0; size = sizehdr+size0+size1;
		}
		pip->bKeepPrevContacts = false;
	}

	job.pPendingJobs[job.ijob=ijob]->pent0last = pent0;
	CMemStreamA stm(job.pPendingJobs[ijob]->bufin+job.pPendingJobs[ijob]->szin, job.szinMax-job.pPendingJobs[ijob]->szin);

	SaveGeomToJobBuf(stm,this,ioffs0,ipass,ijob);
	SaveGeomToJobBuf(stm,pCollider,ioffs1,ipass,ijob);

	stm.Write(pdata1, sizeof(geom_world_data));
	stm.Write(pdata2, sizeof(geom_world_data));
	stm.Write(pip, sizeof(intersection_params));
	stm.Write(pent0); stm.Write(ipart0);
	stm.Write(pent1); stm.Write(ipart1);

	job.pPendingJobs[ijob]->szin += stm.m_iPos;
	job.pPendingJobs[ijob]->ipass = ipass;
	job.pPendingJobs[ijob]->ientries += 1+(-iszero((int)bKeepPrevContacts)); 
	pip->bKeepPrevContacts=bKeepPrevContacts;

	return 0;
}
#endif// !defined(__SPU__)

#ifndef PS3 
#define _ALIGNVC(a) __declspec(align(a))


#endif

#if defined(PHYS_JOB_SIMULATION) || defined(__SPU__)
# define ALIGN_SIZE(x,y) (((x)+((y)-1))&~((y)-1))
  _ALIGNVC(128) char g_JobBufIntersect[MAX_PHYS_THREADS+1][ALIGN_SIZE(MAX_JOB_SIZE_INTERSECT+4,128)] JOB_LOCAL("RB_GeomIntersect","RB_PrimIntersect") _ALIGN(128); 
  _ALIGNVC(128) char g_JobBufIntersectAux[MAX_PHYS_THREADS+1][ALIGN_SIZE(MAX_JOB_SIZE_INTERSECT_AUX,128)] JOB_LOCAL("RB_Kernel","RP_Kernel") _ALIGN(128); 
  _ALIGNVC(128) char g_JobBufSolver[MAX_PHYS_THREADS+1][ALIGN_SIZE(MAX_JOB_SIZE_SOLVER,128)] JOB_LOCAL("RB_Solver") _ALIGN(128);
  _ALIGNVC(128) char g_JobBufCloth[MAX_PHYS_THREADS+1][ALIGN_SIZE(MAX_JOB_SIZE_CLOTH,128)] JOB_LOCAL("Cloth") _ALIGN(128);
  _ALIGNVC(128) char g_JobBufRope[MAX_PHYS_THREADS+1][ALIGN_SIZE(MAX_JOB_SIZE_ROPE,128)] JOB_LOCAL("Rope") _ALIGN(128);
  _ALIGNVC(128) char g_JobBufFS[MAX_PHYS_THREADS+1][ALIGN_SIZE(MAX_JOB_SIZE_FS,128)] JOB_LOCAL("Featherstone") _ALIGN(128);
  _ALIGNVC(128) char g_AuxJobBuffer[1536] SPU_LOCAL _ALIGN(128);
  int g_UseKernelJob SPU_LOCAL;
  volatile SAuxillaryJobInfo g_AuxJobInfo SPU_LOCAL; 
# undef ALIGN_SIZE
#else
	char g_JobBufIntersect[MAX_PHYS_THREADS+1][1];
  char g_JobBufIntersectAux[MAX_PHYS_THREADS+1][1]; 
	char g_JobBufSolver[MAX_PHYS_THREADS+1][1];
	char g_JobBufCloth[MAX_PHYS_THREADS+1][1];
	char g_JobBufRope[MAX_PHYS_THREADS+1][1];
	char g_JobBufFS[MAX_PHYS_THREADS+1][1];
#endif
























































































































int ReportJobContacts(char *bufout)
{
	int iCaller = get_iCaller();
	if (!g_nTotContacts)
		return 0;
	char *pbuf = SPU_LOCAL_PTR((char*)(&g_idata[iCaller]));
	geom_contact *pcont;
	int i,sz,ncont,sztot=0;
	INT_PTR pAreaBuf,pAreaPtBuf,pAreaPrimBuf0,pAreaPrimBuf1,pAreaFeatureBuf0,pAreaFeatureBuf1,pBrdPtBuf,pBrdiTriBuf;
	*(int*)pbuf = ncont=g_nTotContacts; sztot += sizeof(int)*2;
	memmove(pbuf+sztot, g_ContPent[0], sz=ncont*sizeof(g_ContPent[0][0])); sztot+=sz;
	memmove(pbuf+sztot, g_ContPent[1], sz); sztot+=sz;
	memmove(pbuf+sztot, g_ContiPart, sz=ncont*sizeof(g_ContiPart[0])); sztot+=sz;
	memmove(pcont=(geom_contact*)(pbuf+sztot), g_Contacts, sz=ncont*sizeof(geom_contact)); sztot+=sz;
	memmove(pbuf+sztot, g_AreaBuf, sz=g_nAreas*sizeof(geom_contact_area)); pAreaBuf=(INT_PTR)pbuf+sztot; sztot+=sz;
	memmove(pbuf+sztot, g_AreaPtBuf, sz=g_nAreaPt*sizeof(Vec3)); pAreaPtBuf=(INT_PTR)bufout+sztot; sztot+=sz;
	memmove(pbuf+sztot, g_AreaPrimBuf0, sz=g_nAreaPt*sizeof(int)); pAreaPrimBuf0=(INT_PTR)bufout+sztot; sztot+=sz;
	memmove(pbuf+sztot, g_AreaFeatureBuf0, sz); pAreaFeatureBuf0=(INT_PTR)bufout+sztot; sztot+=sz;
	memmove(pbuf+sztot, g_AreaPrimBuf1, sz); pAreaPrimBuf1=(INT_PTR)bufout+sztot; sztot+=sz;
	memmove(pbuf+sztot, g_AreaFeatureBuf1, sz); pAreaFeatureBuf1=(INT_PTR)bufout+sztot; sztot+=sz;
	memmove(pbuf+sztot, g_BrdPtBuf, (sz=g_BrdPtBufPos)*sizeof(Vec3)); pBrdPtBuf=(INT_PTR)bufout+sztot; sztot+=sz*sizeof(Vec3);
	memmove(pbuf+sztot, g_BrdiTriBuf, sz*=sizeof(int)*2); pBrdiTriBuf=(INT_PTR)bufout+sztot; sztot+=sz;
	for(i=0;i<ncont;i++) {
		if (pcont[i].parea) {
			int *g_AreaPrimBuf0d,*g_AreaPrimBuf1d,*g_AreaFeatureBuf0d,*g_AreaFeatureBuf1d;
			if (pcont[i].parea->piPrim[0]<pcont[i].parea->piPrim[1]) {
				g_AreaPrimBuf0d=g_AreaPrimBuf0; g_AreaPrimBuf1d=g_AreaPrimBuf1; g_AreaFeatureBuf0d=g_AreaFeatureBuf0; g_AreaFeatureBuf1d=g_AreaFeatureBuf1;
			}	else {
				g_AreaPrimBuf0d=g_AreaPrimBuf1; g_AreaPrimBuf1d=g_AreaPrimBuf0; g_AreaFeatureBuf0d=g_AreaFeatureBuf1; g_AreaFeatureBuf1d=g_AreaFeatureBuf0;
			}
		  pcont[i].parea = (geom_contact_area*)((INT_PTR)pcont[i].parea+pAreaBuf-(INT_PTR)g_AreaBuf);
		  pcont[i].parea->piPrim[0] = (int*)((INT_PTR)pcont[i].parea->piPrim[0]+pAreaPrimBuf0-(INT_PTR)g_AreaPrimBuf0d);
		  pcont[i].parea->piPrim[1] = (int*)((INT_PTR)pcont[i].parea->piPrim[1]+pAreaPrimBuf1-(INT_PTR)g_AreaPrimBuf1d);
		  pcont[i].parea->piFeature[0] = (int*)((INT_PTR)pcont[i].parea->piFeature[0]+pAreaFeatureBuf0-(INT_PTR)g_AreaFeatureBuf0d);
		  pcont[i].parea->piFeature[1] = (int*)((INT_PTR)pcont[i].parea->piFeature[1]+pAreaFeatureBuf1-(INT_PTR)g_AreaFeatureBuf1d);
		  pcont[i].parea->pt = (Vec3*)((INT_PTR)pcont[i].parea->pt+pAreaPtBuf-(INT_PTR)g_AreaPtBuf);
		  pcont[i].parea = (geom_contact_area*)((INT_PTR)pcont[i].parea-(INT_PTR)pbuf+(INT_PTR)bufout);
		}
		if (pcont[i].ptborder) { 
      pcont[i].ptborder = (Vec3*)((INT_PTR)pcont[i].ptborder + 
			(pcont[i].ptborder==&g_Contacts[i].pt || pcont[i].ptborder==&g_Contacts[i].center ?
			 (INT_PTR)pcont-(INT_PTR)g_Contacts+(INT_PTR)bufout-(INT_PTR)pbuf : 
			(g_Contacts[i].parea && pcont[i].ptborder==g_Contacts[i].parea->pt ? 
			 pAreaPtBuf-(INT_PTR)g_AreaPtBuf	:
			 pBrdPtBuf-(INT_PTR)g_BrdPtBuf)));
    }
		if (pcont[i].idxborder) {
      pcont[i].idxborder = (int(*)[2])((INT_PTR)pcont[i].idxborder+pBrdiTriBuf-(INT_PTR)g_BrdiTriBuf);
    }
	}
	sztot = (sztot + 127) & ~127;//align for transfer speed
	((int*)pbuf)[1] = sztot;
	memtransfer_to_main(bufout,pbuf,sztot,0/*id*/);
	memtransfer_sync(0/*id*/);
	return sztot;
}

extern geom_world_data defgwd;
extern intersection_params defip;

extern COverlapChecker g_Overlapper;
CGeometry *LoadGeomFromMemBufOffs(CMemStreamA &stm)
{
#if defined(MEMSTREAM_DEBUG)
	int tag; stm.Read(tag); if (tag != MEMSTREAM_DEBUG_TAG) snPause(); 
#endif 
  CGeometry *pGeom; 
	int ioffs=stm.Read<int>(), ipos0=stm.m_iPos, sz;
  IF (ioffs != 0, 0) { // if referring to mesh that has already been loaded, simply return it.
    stm.m_iPos += ioffs-stm.m_iPos;
	  switch (stm.Read<int>()) {
		  case GEOM_VOXELGRID: 
		  case GEOM_HEIGHTFIELD: 
		  case GEOM_TRIMESH: 
  		  break;
		  default:
			  stm.Read(sz);
        break;
    }
	  pGeom = (CGeometry*)(stm.m_pBuf+stm.m_iPos); 
    stm.m_iPos = ipos0; 
  } else 
    pGeom = LoadGeomFromMem(stm); 
#if defined(MEMSTREAM_DEBUG)
	stm.Read(tag); if (tag != MEMSTREAM_DEBUG_TAG+1) snPause(); 
#endif 
	return pGeom;
}












































































































SPU_ENTRY(RB_PrimIntersect)
void PrimIntersectJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData)
{
#ifdef USE_JOB_DUMPS
	static char fname[] = "gamecrysis2/jobdump0.bin";
	if (FILE *fdump = fopen(fname,"rb")) {
		fname[19]++;
		fseek(fdump,0,SEEK_END);
		szin = ftell(fdump); 
		fseek(fdump,0,SEEK_SET);
		bufin = new char[szin];
		fread(bufin,1,szin,fdump);
		fclose(fdump);
	}
#endif




	SPU_DOMAIN_LOCAL CGeometry *pGeom[2];
	SPU_DOMAIN_LOCAL geom_world_data *pgwd[2];
	SPU_DOMAIN_LOCAL intersection_params *pip;
	SPU_DOMAIN_LOCAL CPhysicalEntity *pent[2];
	SPU_DOMAIN_LOCAL geom_contact *pcont;
	int i,ipart[2],ncont,sz;
	int iCaller = get_iCaller_int();
	//align transfer for speed, bypass cache
	memtransfer_from_main(&g_JobBufIntersect[iCaller], bufin, (szin+127)&~127, 0);

















	memtransfer_sync(0);
	CMemStreamA stm(&g_JobBufIntersect[iCaller],szin);

	g_nTotContacts = 0;
	//initialize here since we do not support job global initializers
	//g_idata[iCaller].Overlapper.iPrevCode = -1;
	new (&defgwd) geom_world_data();
	new (&defip) intersection_params();

	while(*(int*)(stm.m_pBuf+stm.m_iPos)!=-1){
		for(i=0;i<2;i++)
			pGeom[i] = LoadGeomFromMemBufOffs(stm);
		pgwd[0] = SPU_LOCAL_PTR((geom_world_data*)(stm.m_pBuf+stm.m_iPos)); 
    stm.m_iPos += sizeof(geom_world_data);
		pgwd[1] = SPU_LOCAL_PTR((geom_world_data*)(stm.m_pBuf+stm.m_iPos)); 
    stm.m_iPos += sizeof(geom_world_data);
    pip = SPU_LOCAL_PTR((intersection_params*)(stm.m_pBuf+stm.m_iPos)); 
    stm.m_iPos += sizeof(intersection_params);
		pip->bThreadSafe = 1;
		if (!pip->bKeepPrevContacts) {
			if ((*written+(sz=ReportJobContacts(bufout+*written)))>=szout) { 
				for(*written=(*written+sz)|1<<29,g_someJobDone.Set(); *written; ) 



					;
			}
			else if (sz>0) {
				*written += sz; 
				g_someJobDone.Set();
			}
		}
		stm.Read(pent[0]); stm.Read(ipart[0]);
    MEMSTREAM_DEBUG_ASSERT(pent[0]);
		stm.Read(pent[1]); stm.Read(ipart[1]);
    MEMSTREAM_DEBUG_ASSERT(pent[1]);
    MEMSTREAM_DEBUG_ASSERT(pGeom[0]);    
 		ncont = ((CPrimitive*)pGeom[0])->CPrimitive::Intersect(pGeom[1], pgwd[0],pgwd[1], pip, pcont);
		for(; ncont>0; ncont--) for(i=0;i<2;i++) {
      g_ContPent[i][g_nTotContacts-ncont] = pent[i];
			g_ContiPart[g_nTotContacts-ncont][i] = ipart[i];
		}
	}




	const int written_cur = *written;//spare 1 volatile lookup
	SignalOutput(written, (written_cur + ReportJobContacts(bufout+written_cur)) | 1<<30);
	g_someJobDone.Set();






}

SPU_ENTRY(RB_GeomIntersect)
void GeomIntersectJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData)
{
#ifdef USE_JOB_DUMPS
	static char fname[] = "gamecrysis2/jobdump0.bin";
	if (FILE *fdump = fopen(fname,"rb")) {
		fname[19]++;
		fseek(fdump,0,SEEK_END);
		szin = ftell(fdump); 
		fseek(fdump,0,SEEK_SET);
		bufin = new char[szin];
		fread(bufin,1,szin,fdump);
		fclose(fdump);
	}
#endif




	SPU_DOMAIN_LOCAL CGeometry *pGeom[2];
	SPU_DOMAIN_LOCAL geom_world_data *pgwd[2];
	SPU_DOMAIN_LOCAL intersection_params *pip;
	SPU_DOMAIN_LOCAL CPhysicalEntity *pent[2];
	SPU_DOMAIN_LOCAL geom_contact *pcont;
	int i,ipart[2],ncont,sz;
	int iCaller = get_iCaller_int();
	//align transfer for speed, bypass cache
	memtransfer_from_main(&g_JobBufIntersect[iCaller], bufin, (szin+127)&~127, 0);

















	memtransfer_sync(0);
	CMemStreamA stm(&g_JobBufIntersect[iCaller],szin);

	g_nTotContacts = 0;
	//initialize here since we do not support job global initializers
	//g_idata[iCaller].Overlapper.iPrevCode = -1;
	new (&defgwd) geom_world_data();
	new (&defip) intersection_params();

	while(*(int*)(stm.m_pBuf+stm.m_iPos)!=-1){
		for(i=0;i<2;i++)
			pGeom[i] = LoadGeomFromMemBufOffs(stm);
		pgwd[0] = SPU_LOCAL_PTR((geom_world_data*)(stm.m_pBuf+stm.m_iPos)); 
    stm.m_iPos += sizeof(geom_world_data);
		pgwd[1] = SPU_LOCAL_PTR((geom_world_data*)(stm.m_pBuf+stm.m_iPos)); 
    stm.m_iPos += sizeof(geom_world_data);
    pip = SPU_LOCAL_PTR((intersection_params*)(stm.m_pBuf+stm.m_iPos)); 
    stm.m_iPos += sizeof(intersection_params);
		pip->bThreadSafe = 1;
		if (!pip->bKeepPrevContacts) {
			if ((*written+(sz=ReportJobContacts(bufout+*written)))>=szout) { 
				for(*written=(*written+sz)|1<<29,g_someJobDone.Set(); *written; ) 



					;
			}
			else if (sz>0) {
				*written += sz; 
				g_someJobDone.Set();
			}
		}
		stm.Read(pent[0]); stm.Read(ipart[0]);
    MEMSTREAM_DEBUG_ASSERT(pent[0]);
		stm.Read(pent[1]); stm.Read(ipart[1]);
    MEMSTREAM_DEBUG_ASSERT(pent[1]);
    MEMSTREAM_DEBUG_ASSERT(pGeom[0]);    
 		ncont = pGeom[0]->CGeometry::Intersect(pGeom[1], pgwd[0],pgwd[1], pip, pcont);
		for(; ncont>0; ncont--) for(i=0;i<2;i++) {
      g_ContPent[i][g_nTotContacts-ncont] = pent[i];
			g_ContiPart[g_nTotContacts-ncont][i] = ipart[i];
		}
	}




	const int written_cur = *written;//spare 1 volatile lookup
	SignalOutput(written, (written_cur + ReportJobContacts(bufout+written_cur)) | 1<<30);
	g_someJobDone.Set();






}


#ifdef USE_SOLVER_JOB
void InvokeDelayedContactSolver(CMemStream &stm);
SPU_ENTRY(RB_Solver)
void SolverJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData)
{



	int iCaller = get_iCaller_int();
	//align transfer for speed, bypass cache
	memtransfer_from_main(&g_JobBufSolver[iCaller], bufin, (szin+127)&~127, 1/*id*/);
	memtransfer_sync(1/*id*/);

	CMemStream stm(&g_JobBufSolver[iCaller],szin,false);

	while(stm.m_iPos<szin) 
		InvokeDelayedContactSolver(stm);

	memtransfer_to_main(bufin, &g_JobBufSolver[iCaller], (szin+127)&~127,1/*id*/);
	memtransfer_sync(1/*id*/);



	SignalOutput(written, szin|1<<30);
	g_someJobDone.Set();
}
#endif


#ifdef USE_CLOTH_JOB
JOB_LOCAL("Cloth") geom_contact g_ClothContacts[4];
SPU_ENTRY(Cloth)
void ClothJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData)
{



	int iCaller = get_iCaller_int();
	//align transfer for speed, bypass cache
	memtransfer_from_main(&g_JobBufCloth[iCaller], bufin, (szin+127)&~127, 2/*id*/);
	memtransfer_sync(2/*id*/);
	CMemStreamA stm(&g_JobBufCloth[iCaller],szin);






	while(stm.m_iPos<szin)
		CSoftEntity::JobProc(stm);

	memtransfer_to_main(bufin, &g_JobBufCloth[iCaller], (szin+127)&~127,2/*id*/);
	memtransfer_sync(2/*id*/);



	SignalOutput(written, szin|1<<30);
	g_someJobDone.Set();
}
#endif

#ifdef USE_ROPE_JOB
SPU_ENTRY(Rope)
void RopeJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData)
{



	int iCaller = get_iCaller_int();
	//align transfer for speed, bypass cache
	memtransfer_from_main(&g_JobBufRope[iCaller], bufin, (szin+127)&~127, 2/*id*/);
	memtransfer_sync(2/*id*/);
	CMemStreamA stm(&g_JobBufRope[iCaller],szin);























	while(stm.m_iPos<szin)
		CRopeEntity::JobProc(stm);

	memtransfer_to_main(bufin, &g_JobBufRope[iCaller], (szin+127)&~127,2/*id*/);
	memtransfer_sync(2/*id*/);










	SignalOutput(written, szin|1<<30);
	g_someJobDone.Set();
}
#endif

#ifdef USE_FEATHERSTONE_JOB
SPU_ENTRY(Featherstone)
void FeatherstoneJobProc(char *bufin, int szin, char *bufout, int szout, volatile int *written, SJobData* jobData)
{



	int iCaller = get_iCaller_int();
	
	//align transfer for speed, bypass cache
	memtransfer_from_main(&g_JobBufFS[iCaller], bufin, (szin+127)&~127, 2/*id*/);
	memtransfer_sync(2/*id*/);
	CMemStreamA stm(&g_JobBufFS[iCaller],szin);


	while(stm.m_iPos<szin)
		CArticulatedEntity::JobProc(stm);

	memtransfer_to_main(bufin, &g_JobBufFS[iCaller], (szin+127)&~127,2/*id*/);
	memtransfer_sync(2/*id*/);



	SignalOutput(written, szin|1<<30);
	g_someJobDone.Set();
}
#endif

void SaveBVTreeToMem(CBVTree *_pTree, CMemStreamA &stm, int bDefer)
{
  CBVTree *pTree = SPU_MAIN_PTR(_pTree); 
  MEMSTREAM_DEBUG_ASSERT(pTree);
	int itype = pTree->GetType();
	stm.Write(itype);
	if (itype == BVT_OBB) {	COBBTree *pOBBTree = SPU_MAIN_PTR((COBBTree*)pTree);
      stm.Write(SPU_MAIN_PTR(pOBBTree), sizeof(COBBTree));
			stm.Write(SPU_MAIN_PTR(pOBBTree->m_pNodes), pOBBTree->m_nNodes*sizeof(OBBnode), bDefer);
			stm.Write(SPU_MAIN_PTR(pOBBTree->m_pTri2Node), pOBBTree->m_pMesh->m_nTris*sizeof(pOBBTree->m_pTri2Node[0]), bDefer);
      goto end; 
  } 
  if (itype == BVT_AABB) { CAABBTree *pAABBTree = SPU_MAIN_PTR((CAABBTree*)pTree);
      stm.Write(SPU_MAIN_PTR(pAABBTree), sizeof(CAABBTree));
			stm.Write(SPU_MAIN_PTR(pAABBTree->m_pNodes), pAABBTree->m_nNodes*sizeof(AABBnode));
			stm.Write(SPU_MAIN_PTR(pAABBTree->m_pTri2Node), ((pAABBTree->m_pMesh->m_nTris-1>>5-pAABBTree->m_nBitsLog)+1)*sizeof(int));	
      goto end; 
  } 
  if (itype == BVT_SINGLEBOX) {
      stm.Write(SPU_MAIN_PTR(pTree), sizeof(CSingleBoxTree));
      goto end; 
  }
  if (itype == BVT_HEIGHTFIELD) { CHeightfieldBV *pHfBV = SPU_MAIN_PTR((CHeightfieldBV*)pTree);
      stm.Write(SPU_MAIN_PTR(pHfBV->m_pUsedTriMap), ((pHfBV->m_pMesh->m_nTris-1>>5)+1)*sizeof(int), bDefer);
      goto end; 
  } 
  if (itype == BVT_VOXEL) { voxelgrid *pvx = SPU_MAIN_PTR(((CVoxelBV*)pTree)->m_pgrid);
    stm.Write(SPU_MAIN_PTR(pvx->pCellTris), sizeof(int)*(pvx->size.GetVolume()+1), bDefer);
    stm.Write(SPU_MAIN_PTR(pvx->pTriBuf), sizeof(int)*pvx->pCellTris[pvx->size.GetVolume()], bDefer);
    goto end; 
  }	
end: 
#if defined(MEMSTREAM_DEBUG)
  int tag = MEMSTREAM_DEBUG_TAG;
  stm.Write(&tag, sizeof(int));
#endif
  ;
}

CBVTree *LoadBVTreeFromMem(CMemStreamA &stm, CTriMesh *pMesh) 
{
	char *stmBuf = SPU_LOCAL_PTR(stm.m_pBuf);
  MEMSTREAM_DEBUG_ASSERT(pMesh);
	int itype = stm.Read<int>();
	CBVTree *pTree = (CBVTree*)(stmBuf+stm.m_iPos);
  MEMSTREAM_DEBUG_ASSERT(pTree);
	switch (itype) {
		case BVT_OBB: { COBBTree *pOBBTree = (COBBTree*)pTree;
			patch_vmt(pOBBTree);
			stm.m_iPos += sizeof(COBBTree);
			pOBBTree->m_pMesh = pMesh;
			stm.ReadPtr(pOBBTree->m_pNodes, pOBBTree->m_nNodes);
			stm.ReadPtr(pOBBTree->m_pTri2Node, pMesh->m_nTris);
		} break;
		case BVT_AABB: { CAABBTree *pAABBTree = (CAABBTree*)pTree; 
			patch_vmt(pAABBTree);
			stm.m_iPos += sizeof(CAABBTree);
			pAABBTree->m_pMesh = pMesh;
			stm.ReadPtr(pAABBTree->m_pNodes, pAABBTree->m_nNodes);
			stm.ReadPtr(pAABBTree->m_pTri2Node, (pMesh->m_nTris-1>>5-pAABBTree->m_nBitsLog)+1, false);	
		} break;
		case BVT_SINGLEBOX:
			((CSingleBoxTree*)pTree)->m_pGeom = pMesh;
			patch_vmt((CSingleBoxTree*)pTree);
			stm.m_iPos += sizeof(CSingleBoxTree);
			break;
		case BVT_HEIGHTFIELD: { CHeightfieldBV *pHfBV = (CHeightfieldBV*)(pTree = &((CHeightfield*)pMesh)->m_Tree);
			patch_vmt(pHfBV);
			pHfBV->m_pMesh = pMesh;
			pHfBV->m_phf = &((CHeightfield*)pMesh)->m_hf;
			stm.ReadPtr(pHfBV->m_pUsedTriMap, (pMesh->m_nTris-1>>5)+1);
		} break;
		case BVT_VOXEL: { CVoxelBV *pVoxBV = (CVoxelBV*)(pTree = &((CVoxelGeom*)pMesh)->m_Tree);
			patch_vmt(pVoxBV);
			pVoxBV->m_pMesh = pMesh;
			pVoxBV->m_pgrid = &((CVoxelGeom*)pMesh)->m_grid;
			stm.ReadPtr(pVoxBV->m_pgrid->pCellTris, pVoxBV->m_pgrid->size.GetVolume()+1);
			stm.ReadPtr(pVoxBV->m_pgrid->pTriBuf, pVoxBV->m_pgrid->pCellTris[pVoxBV->m_pgrid->size.GetVolume()]);
			pVoxBV->m_pgrid->pVtx = pMesh->m_pVertices;
			pVoxBV->m_pgrid->pIndices = pMesh->m_pIndices;
			pVoxBV->m_pgrid->pNormals = pMesh->m_pNormals;
			pVoxBV->m_pgrid->pIds = pMesh->m_pIds;
		} break;
		default: 
			pTree=NULL; 
			break; 
	}
#if defined(MEMSTREAM_DEBUG)
  if (itype != -1) { int read_tag; stm.Read(read_tag); 
		if (read_tag != MEMSTREAM_DEBUG_TAG) { snPause(); }
	}
#endif 
	return pTree;
}


ILINE void SaveGeomToMem(CGeometry *pGeom, CMemStreamA &stm, int bForRays)
{
#if defined(MEMSTREAM_DEBUG)
  int tag = MEMSTREAM_DEBUG_TAG;
  stm.Write(&tag, sizeof(int));
#endif
	int itype=pGeom->GetType(), sz=sizeof(CTriMesh), i,j;
#ifndef __SPU__
	int bDefer = -(pGeom->m_lockUpdate>>31);


#endif
	stm.Write(itype);
	switch (itype) {
		case GEOM_VOXELGRID: 
      sz=sizeof(CVoxelGeom); 
      goto savemesh;
		case GEOM_HEIGHTFIELD: 
      sz=sizeof(CHeightfield);
			bDefer = 0; ; 
		case GEOM_TRIMESH: {
			savemesh:
			CTriMesh *pMesh = (CTriMesh*)pGeom;
			stm.Write(pGeom, sz);
			stm.Write(pMesh->m_pIndices, pMesh->m_nTris*3*sizeof(pMesh->m_pIndices[0]), bDefer);
			if (pMesh->m_pIds) {
				stm.Write((int)1);
				stm.Write(pMesh->m_pIds, ((pMesh->m_nTris-1&~3)+4)*sizeof(pMesh->m_pIds[0]), bDefer);
			} else 
				stm.Write((int)0);
			stm.Write(pMesh->m_pVertices.data, pMesh->m_nVertices*pMesh->m_pVertices.iStride, bDefer);
			stm.Write(pMesh->m_pNormals, pMesh->m_nTris*sizeof(pMesh->m_pNormals[0]), bDefer);
			stm.Write(pMesh->m_pTopology, pMesh->m_nTris*sizeof(pMesh->m_pTopology[0]), bDefer);
			if (itype==GEOM_TRIMESH && bForRays) {
				stm.Write(-1);
				for(i=0; i<pMesh->m_nHashPlanes; i++) {
					stm.Write(pMesh->m_pHashGrid[i], sizeof(index_t)*(j=pMesh->m_hashgrid[i].size.x*pMesh->m_hashgrid[i].size.y+1), bDefer);
					stm.Write(pMesh->m_pHashData[i], sizeof(index_t)*pMesh->m_pHashGrid[i][j-1], bDefer);
				}
				*stm.GetPtr<int>() = 0;
				stm.m_iPos += (pMesh->m_nTris+31>>5)*4; // space for used triangles array
			} else 
				SaveBVTreeToMem(pMesh->m_pTree, stm, bDefer);
		} break;
		default: 
			stm.Write(sz = pGeom->GetSizeFast());
			stm.Write(pGeom, sz);
	}
#if defined(MEMSTREAM_DEBUG)
  tag = MEMSTREAM_DEBUG_TAG+1;
  stm.Write(&tag, sizeof(int));
#endif
}

SPU_DEFAULT_TO_LOCAL(RWI)
CGeometry *LoadGeomFromMem(CMemStreamA &_stm)
{
	SPU_DOMAIN_LOCAL CMemStreamA &stm(SPU_LOCAL_REF(_stm));
#if defined(MEMSTREAM_DEBUG)
  int read_tag; stm.Read(read_tag);
  if (read_tag != MEMSTREAM_DEBUG_TAG) { snPause(); }
#endif 
	SPU_DOMAIN_LOCAL char *stmBuf = SPU_LOCAL_PTR(stm.m_pBuf);
	int itype=stm.Read<int>(), sz=sizeof(CTriMesh), i,j;
	CGeometry *pGeom = 0;
	switch (itype) {
		case GEOM_VOXELGRID: 
      sz=sizeof(CVoxelGeom);	
			patch_vmt((CVoxelGeom*)(stmBuf+stm.m_iPos));
      goto loadmesh;
		case GEOM_HEIGHTFIELD: {
			CHeightfield *phf = (CHeightfield*)(stmBuf+stm.m_iPos);
			patch_vmt(phf);
			sz = sizeof(CHeightfield);
#ifndef __SPU__
			phf->m_hf.fpGetHeightCallback = getHFPatchHeight;
			phf->m_hf.fpGetSurfTypeCallback = getHFPatchSurfType;
#endif
			int iCaller = get_iCaller();
			g_pHF = phf;
		}
		case GEOM_TRIMESH: {
			patch_vmt((CTriMesh*)(stmBuf+stm.m_iPos));
			loadmesh:
      CTriMesh *pMesh = (CTriMesh*)(stmBuf+stm.m_iPos); stm.m_iPos += sz;
			stm.ReadPtr(pMesh->m_pIndices, pMesh->m_nTris*3);
			stm.Read(i);
			stm.ReadPtr(pMesh->m_pIds, ((pMesh->m_nTris-1&~3)+4)*i);
			pMesh->m_pIds = (char*)((INT_PTR)pMesh->m_pIds & -(INT_PTR)i);
			stm.ReadPtr(pMesh->m_pVertices.data, pMesh->m_nVertices);
			stm.ReadPtr(pMesh->m_pNormals, pMesh->m_nTris);
			stm.ReadPtr(pMesh->m_pTopology, pMesh->m_nTris);
			pMesh->m_pForeignIdx=0; pMesh->m_pVtxMap=0; pMesh->m_pMeshUpdate=0; 
			if (pMesh->m_pTree = LoadBVTreeFromMem(stm, pMesh))
				pMesh->m_nHashPlanes = 0;
			else {
				for(i=0; i<pMesh->m_nHashPlanes; i++) {
					stm.ReadPtr(pMesh->m_pHashGrid[i], j=pMesh->m_hashgrid[i].size.x*pMesh->m_hashgrid[i].size.y+1);
					stm.ReadPtr(pMesh->m_pHashData[i], pMesh->m_pHashGrid[i][j-1]);
				}
				pMesh->m_pUsedTriMap = (unsigned int*)(stmBuf+stm.m_iPos); stm.m_iPos += (pMesh->m_nTris+31>>5)*4;
			}
			pGeom = pMesh;
		} break;
		default:
			stm.Read(sz);
			pGeom = (CGeometry*)(stmBuf+stm.m_iPos); 
#ifdef USE_JOB_DUMPS
			switch (sz) {
				case sizeof(CBoxGeom): patch_vmt((CBoxGeom*)pGeom); patch_vmt(&((CBoxGeom*)pGeom)->m_Tree); break;
				case sizeof(CCylinderGeom): patch_vmt((CCylinderGeom*)pGeom); patch_vmt(&((CCylinderGeom*)pGeom)->m_Tree); break;
				case sizeof(CSphereGeom): patch_vmt((CSphereGeom*)pGeom); patch_vmt(&((CSphereGeom*)pGeom)->m_Tree); break;
				case sizeof(CRayGeom): patch_vmt((CRayGeom*)pGeom); patch_vmt(&((CRayGeom*)pGeom)->m_Tree); break;
			}
#endif
			stm.m_iPos += sz;
	}
#if defined(MEMSTREAM_DEBUG)
  stm.Read(read_tag);
  if (read_tag != MEMSTREAM_DEBUG_TAG+1) { snPause(); }
#endif 
	return pGeom;
}

#ifndef __SPU__
int *g_pAllGroupsFinished = 0;
int FindJobQueue(int sz, CMemStreamA &stm, int ipass)
{
	if (sz>g_JobMgr.jobs[ipass].szinMax)
		return -1;
#ifndef PHYS_JOB_SIMULATION
	if(!gEnv->GetJobManager()->InvokeAsSPUJob(g_jobName[ipass]))
		return -1;
#endif
	
	int i=0,ijob=-1, retries=0; 
	SJob &job=g_JobMgr.jobs[ipass]; 
	SJobData **pJobData=job.pPendingJobs; 
	if (sz<0 && job.nPendingJobs > job.ijob) { 
		ijob=job.ijob;
		goto found; 
	} else {
	retry: 
		for (ijob=0; ijob<job.nPendingJobs && pJobData[ijob]->szin+sz > job.szinMax; ++ijob);
		for (i=ijob+1; i<job.nPendingJobs; ++i) { 
			if (job.szinMax-pJobData[i]->szin > job.szinMax-pJobData[ijob]->szin)
				ijob=i; 
		} 
		if (ijob>=job.nPendingJobs) { 
			if (ipass!=1) WaitForJobFinish(*g_pAllGroupsFinished,0);
			if (job.nPendingJobs<job.nJobSlots) { 
				if ((pJobData[ijob=job.nPendingJobs++]=g_JobMgr.AcquireJobData(ipass))!=NULL) 
					goto found; 
				--job.nPendingJobs; 
			}
			IF (retries++==0 && ipass!=1 && g_JobMgr.nSubmissionRetries++<MAX_SUBMISSION_RETRIES, 1) { WaitForJobFinish(*g_pAllGroupsFinished,1); goto retry; }
		} else goto found;
	}
	return -1; 
found: 
	pJobData[ijob]->ipass=ipass; 
	stm.m_pBuf = pJobData[ijob]->bufin+pJobData[ijob]->szin;
	stm.m_iPos = 0;
	stm.m_nSize = job.szinMax-pJobData[ijob]->szin;
	stm.SetMeasureOnly(0);
	pJobData[ijob]->szin += max(sz,0);
	return job.ijob=ijob;
}

int MaxJobSize(int ipass) { return g_JobMgr.maxJobSize[ipass]; }
void UpdateJobBuf(int ipass, int ijob, int szAdd) { g_JobMgr.jobs[ipass].pPendingJobs[ijob]->szin += szAdd; }

int WaitForJobFinish(int &bAllGroupsFinished,int bWait)
{
	FUNCTION_PROFILER( GetISystem(),PROFILE_PHYSICS );
	int i,j,nworking,npending,ijob;
	IF (g_JobMgr.nJobs == 0, 0) return -1; // early out in case not initialized yet
	bool bInvokedOnSPU=false; 




	if (&bAllGroupsFinished)
		g_pAllGroupsFinished = &bAllGroupsFinished;
	for(npending=nworking=i=0; i<JOB_COUNT; npending+=g_JobMgr.jobs[i].nPendingJobs,nworking+=g_JobMgr.jobs[i].nActiveJobs,++i) for(j=0; j<g_JobMgr.jobs[i].nPendingJobs; ++j) 
		if (g_JobMgr.jobs[i].nActiveJobs<g_JobMgr.jobs[i].nJobSlots) { 
		SJob &job = g_JobMgr.jobs[i]; SJobData *jobData=job.pPendingJobs[j]; 
		IF (jobData->szin==0, 0) { // prune empty pending jobs
			g_JobMgr.ReleaseJobData(jobData); 
			swap(job.pPendingJobs[j--], job.pPendingJobs[--job.nPendingJobs]);
			continue; 
		} 
		*(int*)(jobData->bufin+jobData->szin) = -1;
		jobData->bWorking=1; jobData->szin0=jobData->szin; jobData->szin=0; 
		jobData->nUsedGeoms=0; jobData->pent0last=0; jobData->written=jobData->read=0; 
		IF (jobData->stmAux,0) jobData->stmAux->m_iPos = 0;
		g_JobMgr.split(jobData->bufin, jobData->szin0+((4&-iszero(i)|4&-iszero(i-5))),128); 
		if (bInvokedOnSPU) {










































































































		}	else { 
#   if defined(PHYS_JOB_SIMULATION)
			do { 
				for (int k=0; k<g_JobMgr.nJobs; ++k) { 
					SFakeSPUTask &jobTask = g_JobMgr.fakeSpus[k]; 
					if (!jobTask.m_job) { 
						jobTask.m_job=jobData; 
						jobTask.m_start.Set(); 
						goto done; 
					}
				}
				g_someJobDone.Wait(150); 
			} while (true);
		  done:;
#   endif // defined(PHYS_JOB_SIMULATION)
		}
		job.pActiveJobs[job.nActiveJobs++]=jobData;
		swap(job.pPendingJobs[j--], job.pPendingJobs[--job.nPendingJobs]);
	} 
	if (!(nworking + npending))
		return -1;
	do { 
		for (i=nworking=0; i<JOB_COUNT; ++i) for(j=0,ijob=-1;j<g_JobMgr.jobs[i].nActiveJobs; ++j) { 
			SJob &job=g_JobMgr.jobs[i];
			SJobData *jobData=job.pActiveJobs[j];
			if (jobData->written>jobData->read) {
				for(; jobData->read<(jobData->written & (1<<29)-1); ) {
					int sz;
					if (i==0 || i==5) {
						// report back the results to pent0
						int offs = jobData->read;
						int ncont = *(int*)(jobData->bufout+offs); offs+=sizeof(int);
						sz = *(int*)(jobData->bufout+offs);	offs+=sizeof(int);
						CPhysicalEntity *pent0 = *(CPhysicalEntity**)(jobData->bufout+offs); offs+=ncont*sizeof(pent0);
						CPhysicalEntity **pents = (CPhysicalEntity**)(jobData->bufout+offs); offs+=ncont*sizeof(pents[0]);
						int (*iparts)[2] = (int(*)[2])(jobData->bufout+offs); offs+=ncont*sizeof(iparts[0]);
						geom_contact *pcontacts = (geom_contact*)(jobData->bufout+offs);
#ifndef USE_JOB_DUMPS
						pent0->DelayedIntersect(pcontacts,ncont, pents,iparts);
#endif
					} else if (i==1) {
						// report solver results
						CMemStream stm(jobData->bufin+jobData->read, jobData->szin0-jobData->read, false);
						int nContacts,nBodies,nEnts,iGroup;
						entity_contact **pContacts;
						RigidBody **pBodies;
						float dt,Ebefore,fixedDamping;
						nContacts = ReadDelayedSolverResults(stm, pContacts,pBodies);
						iGroup = g_pPhysWorlds[0]->ReadDelayedSolverResults(*jobData->stmAux, dt,Ebefore,nEnts,fixedDamping, pContacts,pBodies);
						nBodies = InvokeContactSolver(dt,&g_pPhysWorlds[0]->m_vars, Ebefore, pContacts,nContacts,pBodies);
						g_pPhysWorlds[0]->ProcessIslandSolverResults(iGroup,-1,dt,Ebefore,nEnts,fixedDamping,bAllGroupsFinished,pContacts,nContacts,nBodies,
							get_iCaller_int(),0);
						sz = stm.m_iPos;
					}	else if (i>=2) {
						CMemStreamA stm(jobData->bufin+jobData->read, jobData->szin0-jobData->read);
#if defined(MEMSTREAM_DEBUG)
						{ int tag; stm.Read(tag); if (tag != MEMSTREAM_DEBUG_TAG) snPause(); }
#endif 
						CPhysicalEntity *pent = stm.Read<CPhysicalEntity*>();
						pent->OnDelayedStep(stm);
						sz = stm.m_iPos;
					}
					jobData->read += sz;
				}
				if (jobData->written & 1<<30) {
					ijob=j;
					jobData->bWorking = 0;
					if (jobData->stmAux) jobData->stmAux->m_iPos = 0;
					swap(job.pActiveJobs[j--], job.pActiveJobs[--job.nActiveJobs]);
					g_JobMgr.ReleaseJobData(jobData); 
#         if !defined(__SPU__) && defined(PHYS_JOB_PROFILER_ENABLED)
					if (g_pPhysWorlds[0]) { 
						const int64 elapsed = CryGetTicks()-jobData->ticksStart; 
						g_pPhysWorlds[0]->GetJobProfileInst(i).nTicks += jobData->ticks;
						g_pPhysWorlds[0]->GetJobProfileInst(i).nLatencyAbs += elapsed-jobData->ticks; 
						g_pPhysWorlds[0]->GetJobProfileInst(i).nLatency += (elapsed-(g_timeFiber-jobData->yieldStart))-jobData->ticks; 
					}
#         endif 
				} else {
					if (jobData->written & 1<<29)
						jobData->written=jobData->read = 0;
					++nworking; // resume the job if it still has something to do
				}
			}	else nworking += jobData->bWorking;
		}
#ifndef PHYS_JOB_SIMULATION
    // Give other thread a chance to run if intstructed to wait and no free slot available 
		if(ijob==-1 && bWait && nworking) { YIELD_PHYSFIBER();	}
#endif
	} while(ijob==-1 && nworking && bWait);
	return ijob & -iszero(npending);
}
void CreateJobThreads(int nPoolSize, int numJobs) {
  g_JobMgr.nSubmissionRetries = 0; 
	IF (g_JobMgr.nPoolSize==nPoolSize && g_JobMgr.nJobs==numJobs, 1) 
		return; 
	g_JobMgr.Initialize(numJobs, nPoolSize); 
}

namespace PhysicsSPUBuffer
{
	void GetMemoryUsage( ICrySizer *pSizer ) 
	{
		if (g_JobMgr.pPool) pSizer->AddObject(g_JobMgr.pPool, g_JobMgr.nPoolSize);
		for(int i=0; i<JOB_COUNT; i++) {
			if (g_JobMgr.jobs[i].pPendingJobs) pSizer->AddObject(g_JobMgr.jobs[i].pPendingJobs, sizeof(SJobData*)*g_JobMgr.jobs[i].nJobSlots);
			if (g_JobMgr.jobs[i].pActiveJobs) pSizer->AddObject(g_JobMgr.jobs[i].pActiveJobs, sizeof(SJobData*)*g_JobMgr.jobs[i].nJobSlots);




		}	
#   if defined(PHYS_JOB_SIMULATION)
		if (g_JobMgr.fakeSpus) pSizer->AddObject(g_JobMgr.fakeSpus, g_JobMgr.nJobs*sizeof(SFakeSPUTask)); 
#   endif 
	}
}
#endif//__SPU__

#else
int WaitForJobFinish(int&,int) { return -1; }
void CreateJobThreads(int, int) {}
CMemStream &GetJobAuxStream(int) { static CMemStream dummy; return dummy; }
#endif

// ropes and cloth: split into CheckCollisions(checkParts) and Solve() functions; upload the entire object after checkParts if filled
#undef MAX_JOB_DATA_COUNT
